import pymongo
from bson.objectid import ObjectId

"""
NoSQL，全称 Not Only SQL，意为不仅仅是 SQL，泛指非关系型数据库。NoSQL 是基于键值对的，而且不需要经过 SQL 层的解析，数据之间没有耦合性，性能非常高。

非关系型数据库又可细分如下：

键值存储数据库：其代表有 Redis、Voldemort 和 Oracle BDB 等。
列存储数据库：其代表有 Cassandra、HBase 和 Riak 等。
文档型数据库：其代表有 CouchDB 和 MongoDB 等。
键值存储数据库：其代表有 Redis、Voldemort 和 Oracle BDB 等。
图形数据库：其代表有 Neo4J、InfoGrid 和 Infinite Graph 等。
对于爬虫的数据存储来说，一条数据可能存在某些字段提取失败而缺失的情况，而且数据可能随时调整。另外，数据之间还存在嵌套关系。如果使用关系型数据库存储，一是需要提前建表，二是如果存在数据嵌套关系的话，需要进行序列化操作才可以存储，这非常不方便。如果用了非关系型数据库，就可以避免一些麻烦，更简单、高效

"""

"""
MongoDB 是由 C++ 语言编写的非关系型数据库，是一个基于分布式文件存储的开源数据库系统，其内容存储形式类似 JSON 对象，它的字段值可以包含其他文档、数组及文档数组
在开始之前，请确保已经安装好了 MongoDB 并启动了其服务，安装方式可以参考：https://setup.scrape.center/mongodb。
pip3 install pymongo

"""
"""
连接方式2
client = MongoClient('mongodb://用户名:密码@主机:端口/认证数据库名')
    用户名：你在 mongo 里创建的用户名
    密码：对应密码
    主机：如 localhost
    端口：如 27017
    认证数据库名：通常是 admin 或你建用户时选择的数据库
"""

# 连接mongo
# authSource 是认证数据库，一般来说是admin
client = pymongo.MongoClient(host='localhost', port=27017, username="admin", password="admin123", authSource="admin")

"""
连接方式2
client = MongoClient('mongodb://用户名:密码@主机:端口/认证数据库名')
    用户名：你在 mongo 里创建的用户名
    密码：对应密码
    主机：如 localhost
    端口：如 27017
    认证数据库名：通常是 admin 或你建用户时选择的数据库
"""

# 指定数据库  test 是数据库名称 test可以本身不存在 ，自己会创建
# db = client.test
db = client['test']

# MongoDB 的每个数据库又包含许多集合（collection），它们类似于关系型数据库中的表。
# 指定集合  集合可以本身不存在 ，自己会创建
# collection = db.students
collection = db['students']

# 插入1条数据  insert_one
student = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}
# collection.insert_one(student)

# 插入多条数据， insert_many 传入列表数据
student1 = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}

student2 = {
    'id': '20170202',
    'name': 'Mike',
    'age': 21,
    'gender': 'male'
}
# collection.insert_many([student1, student2])


# 查询单个数据
# find_one 或 find 方法进行查询，其中 find_one 查询得到的是单个结果，find 则返回一个生成器对象
stu = collection.find_one({'name': 'Mike'})  # 通过name查询
print(type(stu))  # <class 'dict'>
print(stu.get('age'))  # 21
stu1 = collection.find_one({'_id': ObjectId('683985d807c6129ad0aedc36')})  # 通过id 查询
print(stu1)

# 查询多个数据
stus = collection.find({'age': 20})  # 返回结果是 Cursor 类型，它相当于一个生成器，我们需要遍历取到所有的结果
print(stus)
for stu in stus:
    print(stu)

### 比较复杂的查询条件####  start


# 如果要查询年龄大于 20 的数据，则写法如下：
stus = collection.find({'age': {'$gt': 20}})
print(
    stus.next())  # {'_id': ObjectId('683985d807c6129ad0aedc37'), 'id': '20170202', 'name': 'Mike', 'age': 21, 'gender': 'male'}

"""
  所有比较符号总结

$lt	小于	    {'age': {'$lt': 20}}
$gt	大于	    {'age': {'$gt': 20}}
$lte 小于等于	{'age': {'$lte': 20}}
$gte 大于等于	{'age': {'$gte': 20}}
$ne	不等于	{'age': {'$ne': 20}}
$in	在范围内	{'age': {'$in': [20, 23]}}
$nin 不在范围内	{'age': {'$nin': [20, 23]}}
"""

# 另外，还可以进行正则匹配查询。例如，查询名字以 M 开头的学生数据
results = collection.find({'name': {'$regex': '^M.*'}})
print(
    results.next())  # {'_id': ObjectId('683985d807c6129ad0aedc37'), 'id': '20170202', 'name': 'Mike', 'age': 21, 'gender': 'male'}

"""
功能符号再归类为下表
符　　号	含　　义	示　　例	示例含义
$regex	匹配正则表达式	{'name': {'$regex': '^M.*'}}	name 以 M 开头
$exists	属性是否存在	{'name': {'$exists': True}}	name 属性存在
$type	类型判断	{'age': {'$type': 'int'}}	age 的类型为 int
$mod	数字模操作	{'age': {'$mod': [5, 0]}}	年龄模 5 余 0
$text	文本查询	{'$text': {'$search': 'Mike'}}	text 类型的属性中包含 Mike 字符串
$where	高级条件查询	{'$where': 'obj.fans_count == obj.follows_count'}	自身粉丝数等于关注数

关于这些操作的更详细用法，可以在 MongoDB 官方文档找到： https://docs.mongodb.com/manual/reference/operator/query/。
"""
### 比较复杂的查询条件#### end

# 计数:  要统计查询结果有多少条数据，可以调用 count_documents 方法
count = collection.count_documents({'name': {'$regex': '^M.*'}})
print(count)  # 1

# 排序: 直接调用 sort 方法，并在其中传入排序的字段及升降序标志即可
results = collection.find().sort('name', pymongo.ASCENDING)  # 升序
print([result['name'] for result in results])

# 忽略跳过元素:  在某些情况下，我们可能想只取某几个元素，这时可以利用 skip 方法偏移几个位置，比如偏移 2，就忽略前两个元素，得到第三个及以后的元素
results = collection.find().sort('name', pymongo.ASCENDING).skip(2)
print([result['name'] for result in results])

# 限制元素个数:  还可以用 limit 方法指定要取的结果个数
results = collection.find().sort('name', pymongo.ASCENDING).skip(2).limit(1)
print([result['name'] for result in results])

# 更新: 我们可以使用 update 方法，指定更新的条件和更新后的数据
"""
# update_one 更新1条
# update_many 更新多条
必须使用更新操作符（如 $set, $inc, $push 等），否则会替换整个文档！
如 {'$set': {'field': value}} 是对字段赋新值。
返回值是一个 UpdateResult 对象，可以用 .matched_count 和 .modified_count 查看匹配条数和实际更新条数。
"""
# 更新例子:  把 name 为 "Mike" 的用户的 age 字段设为 25
# result = collection.update_one(
#     {'name': 'Mike'},           # 查询条件
#     {'$set': {'age': 25}}      # 更新内容
# )
# print(result.matched_count)   # 匹配到的文档数
# print(result.modified_count)  # 实际修改的文档数


# 更新例子: 这里指定查询条件为年龄大于 20，然后更新条件为 {'$inc': {'age': 1}}，也就是年龄加 1，执行之后会将第一条符合条件的数据年龄加 1
condition = {'age': {'$gt': 20}}
result = collection.update_one(condition, {'$inc': {'age': 1}})
print(result)
print(result.matched_count, result.modified_count)

"""
删除
delete_one 即删除第一条符合条件的数据，delete_many 即删除所有符合条件的数据。它们的返回结果都是 DeleteResult 类型，可以调用 deleted_count 属性获取删除的数据条数。
"""
result = collection.delete_one({'name': 'Kevin'})
print(result)
print(result.deleted_count)
result = collection.delete_many({'age': {'$lt': 25}})
print(result.deleted_count)

"""
 其他操作!!!
 另外，PyMongo 还提供了一些组合方法，如 find_one_and_delete、find_one_and_replace 和 find_one_and_update，它们是查找后删除、替换和更新操作，其用法与上述方法基本一致。

 另外，还可以对索引进行操作，相关方法有 create_index、create_indexes 和 drop_index 等。

关于 PyMongo 的详细用法，可以参见官方文档：http://api.mongodb.com/python/current/api/pymongo/collection.html。

另外，还有对数据库和集合本身等的一些操作，这里不再一一讲解，可以参见官方文档：http://api.mongodb.com/python/current/api/pymongo/。
"""
